--[[Infos

	######################
	#   Spacetech`s Prop Ghost Script    #
	#	 Modified (alot) by Kyzer         #
	######################


============================= CONTROLS ================================

	KEY			ACTION

	Forward		Forward
	Backward		Backward
	Left			Left
	Right			Right
	Jump			Up
	Crouch		Down
	Sprint		Hover
	Sprint + Crouch	Airbreak
	Prim Fire		Sounds (if enabled) and explosions (if enabled and if player is admin)
	Sec Fire		Change force (switch between 15%, 50%, 100%)
	Use			Explode, stop ghosting, and remove the entity
	Reload		Ghost aimed entity

========================== END OF CONTROLS ==========================

============================ OTHERS INFOS ===========================

There are concommands for admins only:

	ghost_toggle_adminonly		-> Toggle admin-only
	ghost_toggle_hovermode	-> Toggle hover (and airbreak)
	ghost_toggle_sounds		-> Toggle sounds
	ghost_toggle_effects		-> Toggle effects
	

Changelog (since 1.5):

1.8:	[fix] misc bugs caused by being too far away from where you started ghosting (missing sounds, prop disappearing, etc)
	[fix] bug while pressing second fire key in multiplayer
	[fix] now you get the kill each time you hit and kill someone (was working but not often)
	[add] force multiplied by 6 when ghosting ragdolls (so you can move them as others props)
	[add] uses ulx.logString to record when player start/stop ghosting, (only if server is using ULX of course)
	[change] some keys are handled in the KeyPress hook now (keys that need to be pressed once, not constantly)
	[change] you can no longer ghost cameras (look near the line 251 if you want to try ghosting a camera, there is a funny "bug" if you press Use)
	
1.7:	[fix] fixed bug when you hit walls with breakable props
	[add] a small advert when players join your server :)
	
1.6:	[fix] fixed sound bug
	[add] remember your speed setting when switching props

1.5:	[initial release] first version of my modification of Spacetech's script.
	
	
Known bugs:

	- Can't find any atm, contact me (Kyzer) if you find any.

======================================================================]]


--========================= CONFIGURATION ===========================

--Command: Chat command will become "chat_prefixconsole_command"
local console_command	= "ghost"
local chat_prefix 		= "/"


--Admin only: Set to true to make the whole ghost script admin-only
local admins_only		= false

--Hovermode: Set to false to disable the hovermode and airbreak
local enable_hovermode	= true

--Effects:	It's admin only. Set effects_damage to false if you don't want to cause damages.
--		Place others effects in the effects_list if you want. 
--		See > http://www.garrysmod.com/wiki/?title=Util.Effect <
--		Effect will be pick randomly in this list.
local enable_effects	= false
local effects_damage	= true
local effects_list		= { "Explosion" }


--Sounds:	Set enable_sounds to false if you want to disable sounds
--		Place here a list of sounds you want to hear when pressing
--		the primary fire key (it will be picked randomly in this list)
local enable_sounds		= false
local sounds_list		= { "vo/npc/male01/yeah02.wav", "vo/npc/male01/ohno.wav" }


--Don't change this:
local script_version = "1.8"

--====================== END OF CONFIGURATION =========================



local Ghost = {}
Ghost.Ent = {}
Ghost.Force = {}



local function GhostPrecacheSounds()

	if (enable_sounds) then
		local precached = {}
		for _, v in pairs(sounds_list) do
			if (!precached[v]) then
				util.PrecacheSound(v)
				precached[v] = true
			end
		end
	end
end



local function GhostCheckEntityOwner( ply, entity )

	for k, v in pairs(g_SBoxObjects) do
		for _, i in pairs(v) do
			for _, j in pairs(i) do
				if(j == entity) then
					if(k == ply:UniqueID()) then
						return true
					end
				end
			end
		end
	end
	return false
end



local function GhostDisallowSpawning( ply )

	if (Ghost.Ent[ply:SteamID()]) then
		return false
	end
end
hook.Add( "PlayerSpawnProp",	"GhostDisallowSpawning_Prop_Hook",		GhostDisallowSpawning )
hook.Add( "PlayerSpawnVehicle",	"GhostDisallowSpawning_Vehicle_Hook",	GhostDisallowSpawning )
hook.Add( "PlayerSpawnNPC",		"GhostDisallowSpawning_NPC_Hook",		GhostDisallowSpawning )
hook.Add( "PlayerSpawnRagdoll",	"GhostDisallowSpawning_Ragdoll_Hook",	GhostDisallowSpawning )
hook.Add( "PlayerSpawnSENT",	"GhostDisallowSpawning_SENT_Hook",		GhostDisallowSpawning )
hook.Add( "PlayerSpawnEffect",	"GhostDisallowSpawning_Effect_Hook",	GhostDisallowSpawning )
hook.Add( "PlayerSpawnObject",	"GhostDisallowSpawning_Object_Hook",	GhostDisallowSpawning )



local SUCCESS = 1
local ERROR = 2

local function GhostSendNotify( ply, message, type, print )    

	if( !type ) then type = SUCCESS end

	if (type == SUCCESS) then
		notify_type  = "NOTIFY_GENERIC"
		notify_sound = "ambient/water/drip" .. math.random(1, 4) .. ".wav"

	elseif (type == ERROR) then
		notify_type  = "NOTIFY_ERROR"
		notify_sound = "buttons/button10.wav"
	end
    
	ply:SendLua( "GAMEMODE:AddNotify( \"" .. message .. "\", " .. notify_type .. ", 5 ); surface.PlaySound( \"" .. notify_sound .. "\" )" )

	if ( print ) then
		ply:PrintMessage( HUD_PRINTCONSOLE, message )
	end

end



local function WriteInUlxLog( str )

	if ( ulx ) then
		ulx.logString( str )
	end
	
end



-- I can't think of another way..inside the hook it fucks the airbreak when we press the Ctrl key :/
local function SetPlayerPos()

	local players = player.GetAll()
	for _, ply in pairs(players) do
		local GhostEnt = Ghost.Ent[ply:SteamID()]
		if(GhostEnt && GhostEnt:IsValid()) then
			ply:SetPos(GhostEnt:GetPos())
		end
	end
	
end
timer.Create("Ghost_Timer" , 0.5, 0, SetPlayerPos)



local function GhostCommand( ply, mode )

	if(admin_only && !ply:IsAdmin()) then
		GhostSendNotify(ply, "This command is for admins only!", ERROR)
		return
	end

	local GhostEnt = Ghost.Ent[ply:SteamID()]
	--local GhostPos

	if(GhostEnt != nil && mode == false) then
		--GhostPhysPos.z = GhostPhysPos.z + 30
		--GhostPos = GhostEnt:GetPos()
		GhostEnt:SetNetworkedBool("GhostedProp", false)
		Ghost.Ent[ply:SteamID()] = nil
		ply:Spawn()
		ply:SetPos(GhostPos)
		ply:SetMoveType(MOVETYPE_NOCLIP)
		ply:SetMoveType(MOVETYPE_WALK)
		--ply:ConCommand("noclip\n")
		--ply:ConCommand("noclip\n")
		GhostSendNotify(ply, "You stopped ghosting!")
		WriteInUlxLog( string.format( "%s<%s> stopped ghosting.", ply:Nick(), ply:SteamID() ) )
		return
	end

	local trace = 0

	if(mode == true) then
		local Ang = ply:GetAimVector()
		GhostPos = GhostEnt:GetPos()
		trace = {}
		trace.start = GhostPos + Vector(0, 0, 20) + (Ang * 20)
		trace.endpos = GhostPos + (Ang * 9000)
		trace.filter = GhostEnt
	else
		trace = util.GetPlayerTrace(ply)
	end

	local tr = util.TraceLine(trace)
	local entity = tr.Entity
	
	if((!tr.Hit || !tr.HitNonWorld || !entity:IsValid() || entity:IsNPC() || entity:IsPlayer()
	-- comment the next line if you want to try ghosting cameras
	|| entity:GetClass() == "gmod_cameraprop" || entity:GetClass() == "gmod_rtcameraprop"
	|| entity:GetNetworkedBool("GhostedProp")) || (mode == false && entity == GhostEnt) ) then
		return
	end

	if (!GhostCheckEntityOwner(ply, entity) && !ply:IsAdmin()) then
		GhostSendNotify(ply, "You don't own this entity!", ERROR)
		return
	end

	if(mode == true) then
		GhostEnt:SetNetworkedBool("GhostedProp", false)
	end
	
	GhostEnt = entity

	local MassTotal = 0
	local ConstrainedEntities = constraint.GetAllConstrainedEntities(GhostEnt)

	for _,v in pairs(ConstrainedEntities) do
		if(v:IsValid()) then
			MassTotal = MassTotal + v:GetPhysicsObject():GetMass()
		end
	end

	GhostEnt:SetNetworkedBool("GhostedProp", true)
	GhostEnt:SetNetworkedInt("GhostedPropMass", MassTotal)
	Ghost.Force[ply:SteamID()] = Ghost.Force[ply:SteamID()] or 30

	ply:Spectate(OBS_MODE_CHASE)
	ply:SpectateEntity(GhostEnt)
	local effectdata = EffectData()
	effectdata:SetEntity(GhostEnt)
	util.Effect("propspawn", effectdata)

	if(mode == false) then
		ply:StripWeapons()
		GhostSendNotify(ply, "You are now ghosting!")
	end
	
	WriteInUlxLog( string.format( "%s<%s> is now ghosting a %s", ply:Nick(), ply:SteamID(), GhostEnt:GetModel() )	)
	Ghost.Ent[ply:SteamID()] = GhostEnt

end



local function GhostControl_KeyDown( ply )

	GhostEnt = Ghost.Ent[ply:SteamID()]
	
	if ( !GhostEnt ) then return end
	
	if( !GhostEnt:IsValid() || !ply:IsValid() || !ply:Alive() ) then
		GhostCommand(ply, false)
	else
		ply:DrawViewModel(false)
		ply:DrawWorldModel(false)
		ply:StripWeapons()
		
		GhostEnt:SetPhysicsAttacker(ply)		
		GhostPhys = GhostEnt:GetPhysicsObject()
		GhostPos = GhostEnt:GetPos()
		GhostForce = Ghost.Force[ply:SteamID()]
		GhostForceMul = 1
		
		if (GhostEnt:GetClass() == "prop_ragdoll") then GhostForceMul = 6 end
		Force = GhostEnt:GetNetworkedInt("GhostedPropMass") * GhostForce * GhostForceMul
		
		if(ply:KeyDown(IN_FORWARD)) then
			GhostPhys:ApplyForceCenter(ply:GetAimVector() *  Force)
		end
		
		if(ply:KeyDown(IN_BACK)) then
			GhostPhys:ApplyForceCenter(ply:GetAimVector() * -Force)
		end
		
		if(ply:KeyDown(IN_MOVERIGHT)) then
			GhostPhys:ApplyForceCenter(ply:GetRight() *  Force)
		end
		
		if(ply:KeyDown(IN_MOVELEFT)) then
			GhostPhys:ApplyForceCenter(ply:GetRight() * -Force)
		end
		
		if(ply:KeyDown(IN_JUMP)) then
			GhostPhys:ApplyForceCenter(ply:GetUp() *  Force)
		end
		
		if(ply:KeyDown(IN_DUCK)) then
			GhostPhys:ApplyForceCenter(ply:GetUp() * -Force)
		end
		
		if(ply:KeyDown(IN_SPEED) && enable_hovermode) then
			if(ply:KeyDown(IN_DUCK)) then
				GhostPhys:SetVelocity(Vector(0, 0, 9))
			else
				local Velocity = GhostPhys:GetVelocity()
				GhostPhys:SetVelocity(Vector(Velocity.x, Velocity.y, 9))
			end
		end
		
		if(ply:KeyDown(IN_SPEED)) then
			local Velocity = GhostPhys:GetVelocity()
			GhostPhys:SetVelocity(Vector(Velocity.x, Velocity.y, 9))
		end
		
	end
end



local function GhostThink()

	local players = player.GetAll()
	for _, ply in pairs(players) do
		GhostControl_KeyDown(ply)
	end
	
end
hook.Add("Think", "GhostThink_Hook", GhostThink)



local function GhostControl_KeyPress(ply, key)

	GhostEnt = Ghost.Ent[ply:SteamID()]

	if ( !GhostEnt ) then return end
		
	if( !GhostEnt:IsValid() || !ply:IsValid() || !ply:Alive() ) then
		GhostCommand(ply, false)
	else
		
		GhostPhys = GhostEnt:GetPhysicsObject()
		//GhostPhysPos = GhostPhys:GetPos()
		GhostPos = GhostEnt:GetPos()
		GhostForce = Ghost.Force[ply:SteamID()]
			
		if(key == IN_USE) then
			local effectdata = EffectData()
			--effectdata:SetOrigin( GhostPhysPos )
			effectdata:SetOrigin( GhostPos )
			util.Effect( effects_list[math.random(#effects_list)], effectdata, true, true )
			--util.BlastDamage( ply, ply, GhostPhysPos, 128, 128 )
			util.BlastDamage( ply, ply, GhostPos, 128, 128 )
			--GhostEnt = Ghost.Ent[ply:SteamID()]
			GhostCommand(ply, false)
			GhostEnt:Remove()
		end
		--[[
		if(key == IN_ATTACK) then
			if (ply:IsAdmin()) then
				if(enable_effects) then
					local effectdata = EffectData()
					//effectdata:SetOrigin( GhostPhysPos )
					effectdata:SetOrigin( GhostEnt:GetPos() )
					util.Effect( effects_list[math.random(#effects_list)], effectdata, true, true )
					//if (effects_damage) then util.BlastDamage( ply, ply, GhostPhysPos, 256, 256 )
					if (effects_damage) then
						util.BlastDamage( ply, ply, GhostPos, 256, 256 )
					end
				end
			end
			
			if (enable_sounds) then
				GhostEnt:EmitSound( sounds_list[math.random(#sounds_list)], 100, math.random(90,110))
			end
		end
		]]
		
		if(key == IN_ATTACK) then
			if (ply:IsAdmin() && enable_effects) then
				local effectdata = EffectData()
				effectdata:SetOrigin( GhostPos )
				util.Effect( effects_list[math.random(#effects_list)], effectdata, true, true )
				if (effects_damage) then util.BlastDamage( ply, ply, GhostPos, 256, 256 ) end
			end
			if (enable_sounds) then
				GhostEnt:EmitSound( sounds_list[math.random(#sounds_list)], 100, math.random(90,110))
			end
		end
		
		if(key == IN_ATTACK2) then
			if (GhostForce == 9) then Ghost.Force[ply:SteamID()] = 30
			elseif (GhostForce == 30) then Ghost.Force[ply:SteamID()] = 60
			--elseif (GhostForce == 60) then Ghost.Force[ply:SteamID()] = 300 -- 500 % lol
			else Ghost.Force[ply:SteamID()] = 9
			end
			GhostSendNotify(ply, "Force: "..math.floor(Ghost.Force[ply:SteamID()] / 0.6).."%")
		end
			
		if(key == IN_RELOAD) then
			GhostCommand(ply, true)
		end
		
	end
end
hook.Add( "KeyPress", "GhostControl_KeyPress_Hook", GhostControl_KeyPress )


local function GhostOnPlayerDeath( ply )

	if (Ghost.Ent[ply:SteamID()]) then
		GhostCommand(ply, false)
	end
end
hook.Add( "PlayerDeathThink", "GhostOnPlayerDeath_Hook", GhostOnPlayerDeath )






local function GhostPlayerSay( ply, text )

	if ( text == chat_prefix..console_command ) then
		GhostCommand(ply, false)
		return ""
	end
end
hook.Add("PlayerSay", "GhostPlayerSay_Hook", GhostPlayerSay)



local function CC_GhostCommand( ply )
	GhostCommand(ply, false)
end
concommand.Add(console_command, CC_GhostCommand)



local function CC_GhostToggleAdminOnly( ply )

	if(!ply:IsAdmin()) then
		GhostSendNotify(ply, "This command is for admins only!", ERROR)
		return
	end

	local cmsg
	if (admins_only) then
		admins_only = false
		cmsg = "no longer reserved to admins!"
	else
		admins_only = true
		cmsg = "now reserved to admins!"
	end

	GhostSendNotify(ply, "GhostScript is "..cmsg, SUCCESS, true)
end
concommand.Add("ghost_toggle_adminonly", CC_GhostToggleAdminOnly)



local function CC_GhostToggleHovermode( ply )

	if(!ply:IsAdmin()) then
		GhostSendNotify(ply, "This command is for admins only!", ERROR)
		return
	end

	local cmsg
	if (enable_hovermode) then
		enable_hovermode = false
		cmsg = "disabled!"
	else
		enable_hovermode = true
		cmsg = "enabled!"
	end

	GhostSendNotify(ply, "GhostScript Hovermode is now "..cmsg, SUCCESS, true)
end
concommand.Add("ghost_toggle_hovermode", CC_GhostToggleHovermode)



local function CC_GhostToggleSounds( ply )

	if(!ply:IsAdmin()) then
		GhostSendNotify(ply, "This command is for admins only!", ERROR)
		return
	end

	local cmsg
	if (enable_sounds) then
		enable_sounds = false
		cmsg = "disabled!"
	else
		enable_sounds = true
		GhostPrecacheSounds()
		cmsg = "enabled!"
	end

	GhostSendNotify(ply, "GhostScript Sounds are now "..cmsg, SUCCESS, true)
end
concommand.Add("ghost_toggle_sounds", CC_GhostToggleSounds)



local function CC_GhostToggleEffects( ply )

	if(!ply:IsAdmin()) then
		GhostSendNotify(ply, "This command is for admins only!", ERROR)
		return
	end

	local cmsg
	if (enable_effects) then
		enable_effects = false
		cmsg = "disabled!"
	else
		enable_effects = true
		cmsg = "enabled!"
	end

	GhostSendNotify(ply, "GhostScript Effects are now "..cmsg, SUCCESS, true)
end
concommand.Add("ghost_toggle_effects", CC_GhostToggleEffects)


local function CC_ks_get( ply )
	if(Ghost.Ent[ply:SteamID()]) then
		local up = GhostEnt:GetUp()
		local right = GhostEnt:GetRight()
		Msg( "Up:".. tostring( up ) .. " Right:" .. tostring(right).."\n" ) 
	end
end
concommand.Add("ks_get", CC_ks_get)